#include "HttpContext.h"
#include "Util.h"

// 解析HTTP请求行，使用正则来匹配传入的line参数
// 已提取HTTP请求的方法资源路径、查询字符串和协议版本等下信息
// GET /api/data?id=123 HTTP/1.1\r\n
bool HttpContext::ParseHttpLine(const std::string &line)
{
    std::smatch matches;                                                                                           // 存储匹配结果
    std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase); //icase忽略大小写
    bool ret = std::regex_match(line, matches, e);
    if (ret == false)
    {
        _recv_statu = RECV_HTTP_ERROR;
        _resp_statu = 400;
        return false;
    }
    // 0: GET /biejiuyeke/login?user=xiaoming&passwd=123123 HTTP/1.1 size:60
    // 1: GET size:3
    // 2: /biejiuyeke/login size:17
    // 3: user=xiaoming&passwd=123123 size:27
    // 4: HTTP/1.1 size:8
    //第0个是url本身，第一个是请求方法，第二个是资源路径，第三个是查询字符串，第四个是协议版本

    // 1. 请求方获取
    _request._method = matches[1];
    std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper); //字母都转为大写
    // 2. 资源路径的获取，需要进行url解码操作，不需要+号转空格
    _request._path = Util::UrlDecode(matches[2], false);
    // 3. 协议版本的获取
    _request._version = matches[4];
    // 4. 查询字符串的获取与处理
    std::vector<std::string> query_string_arry;
    std::string query_string = matches[3];
    Util::Split(query_string, "&", &query_string_arry);
    // 针对各个子串，以=符号进行分割，得到key和val，得到之后需要进行URL解码
    for (auto &str : query_string_arry)
    {
        size_t pos = str.find("="); // 查询字符串都是以一种key=val的字符串
        if (pos == std::string::npos)
        {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 400;
            return false;
        }
        std::string key = Util::UrlDecode(str.substr(0, pos), true);
        std::string val = Util::UrlDecode(str.substr(pos + 1), true);
        _request.SetParam(key, val);
    }
    return true;
}
// 接收请求行
bool HttpContext::RecvHttpLine(Buffer *buf)
{
    if (_recv_statu != RECV_HTTP_LINE)
        return false;
    // 1. 获取一行数据，带有末尾的换行
    std::string line = buf->GetLineAndPop(); //这就是为什么之前设计的时候读取带有末尾的换行
    // 2. 需要考虑的的要素：缓冲区中的数据不足一行 或者 一行的数据超大
    if (line.size() == 0) // 说明缓冲区中数据不足一行
    {
        if (buf->ReadAbleSize() > MAX_LIN) // 判断缓冲区中的可读数据长度，如果很长了都不足一行，说明有问题
        {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 414;
            return false;
        }
        // 缓冲区中的数据不足一行，长度也合理，就等待下一次新的数据到来再处理
        return true;
    }
    if (line.size() > MAX_LIN)
    {
        _recv_statu = RECV_HTTP_ERROR;
        _resp_statu = 414;
        return false;
    }
    // 走到这里说明得到了完整的请求行，就可以进行解析了
    bool ret = ParseHttpLine(line);
    if (ret == false)
        return false;
    // 首行处理完毕，进入请求头部获取阶段
    _recv_statu = RECV_HTTP_HEAD;
    return true;
}
// 解析请求头部
bool HttpContext::ParseHttpHead(std::string &line)
{
    if (line.back() == '\n')
        line.pop_back();
    if (line.back() == '\r')
        line.pop_back();
    size_t pos = line.find(": ");
    if (pos == line.npos)
    {
        _recv_statu = RECV_HTTP_ERROR;
        _resp_statu = 400;
        return false;
    }
    std::string key = line.substr(0, pos);
    std::string val = line.substr(pos + 2);
    _request.SetHeader(key, val);
    return true;
}
// 接收请求头部
bool HttpContext::RecvHttpHead(Buffer *buf)
{
    if (_recv_statu != RECV_HTTP_HEAD)
        return false;
    // 一行一行取出数据，直到遇到空行为止，头部的格式 key: val\r\nkey: val\r\n....
    while (1)
    {
        // 1. 获取一行数据
        std::string line = buf->GetLineAndPop();
        // 2. 需要考虑的一些要素：缓冲区中的数据不足一行，获取的一行数据超大
        if (line.size() == 0)
        {
            if (buf->ReadAbleSize() > MAX_LIN)
            {
                _recv_statu = RECV_HTTP_ERROR;
                _resp_statu = 414;
                return false;
            }
            // 缓冲区数据不足一行,符合长度预期，等待新数据的到来
            return true;
        }
        if (line == "\n" || line == "\r\n") // 说明读到了空行
            break;
        bool ret = ParseHttpHead(line);
        if (ret == false)
            return false;
    }
    // 头部处理完毕，进入正文获取阶段
    _recv_statu = RECV_HTTP_BODY;
    return true;
}
// 接收请求正文
bool HttpContext::RecvHttpBody(Buffer *buf)
{
    if (_recv_statu != RECV_HTTP_BODY)
        return false;
    // 获取正文长度
    size_t content_length = _request.ContentLength();
    if (content_length == 0)
    {
        // 没有正文，则请求接收解析完毕
        _recv_statu = RECV_HTTP_OVER;
        return true;
    }
    // 2. 当前已经接收了多少正文，其实就是_request._body中放了多少数据
    size_t real_len = content_length - _request._body.size(); //实际还要接收的正文长度
    // 3. 接收正文放到body中，但是也要考虑当前缓冲区中的数据，是否是全部的正文
    // 3.1 缓冲区中的数据，包含了当前请求的所有正文，则取出数据。缓冲区中也有可能下一个协议的内容
    if (buf->ReadAbleSize() >= real_len)
    {
        _request._body.append(buf->ReadPosition(), real_len);
        buf->MoveReadOffset(real_len);
        _recv_statu = RECV_HTTP_OVER;
        return true;
    }
    // 3.2 缓冲区中数据，无法满足当前正文的需要，数据不足，取出数据，然后等待新数据到来
    _request._body.append(buf->ReadPosition(), buf->ReadAbleSize());
    buf->MoveReadOffset(buf->ReadAbleSize());
    return true;
}

HttpContext::HttpContext()
    : _resp_statu(200), _recv_statu(RECV_HTTP_LINE)
{
}
void HttpContext::ReSet()
{
    _resp_statu = 200;
    _recv_statu = RECV_HTTP_LINE;
    _request.ReSet();
}
// 获取相对应状态码
int HttpContext::RespStatu()
{
    return _resp_statu;
}
// 获取接收状态
HttpRecvStatu HttpContext::HttpContext::RecvStatu()
{
    return _recv_statu;
}
// 获取已经得到的请求信息
HttpRequest &HttpContext::Request()
{
    return _request;
}
// 接收并解析HttpRequest请求
void HttpContext::RecvHttpRequest(Buffer *buf)
{
    // 不同的状态做不同的事情，但是这里不要break，因为处理请求完成之后，应该立即处理头部，而不是退出等待新数据
    switch (_recv_statu)
    {
    case RECV_HTTP_LINE:
        RecvHttpLine(buf);
    case RECV_HTTP_HEAD:
        RecvHttpHead(buf);
    case RECV_HTTP_BODY:
        RecvHttpBody(buf);
    default:
        break;
    }
}